package org.carota.pdf;

import cn.hutool.core.lang.Assert;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.util.IOUtils;
import org.carota.itext5.*;
import org.carota.util.RelateUtil;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * PDF 处理器
 *
 * @author : AnWen
 * @version :1.0
 * @since : 2023-03-28 16:08
 */
@Slf4j
public class PdfProcessor {

    /**
     * 模板位置 支持本地文件和远程文件
     */
    private String templatePath;
    /**
     * @see OssPdfProcessor 支持oss输出 该字段可为空
     * 处理完毕的输出位置 (暂时只支持本地文件)
     */
    private String outputPdfPath;

    /**
     * 初始密码
     */
    private byte[] ownerPassword;

    /**
     * 继续执行次数
     */
    private Integer count = 0;

    /**
     * pdf 阅读器
     */
    private PdfReader reader;
    private PdfStamper stamper;

    /**
     * 缓存输出流
     */
    private final ByteArrayOutputStream bos = new ByteArrayOutputStream ();
    /**
     * 需要执行的配置类列表
     */
    private final List<PdfConfigurer> configurers = new ArrayList<> ();

    private void sort() {
        configurers.sort (Comparator.comparingInt (PdfConfigurer::getOrder));
    }

    //执行所有configurer 最后一个节点写出文件
    //每个configurer 只专注做一个操作
    //配置多个可以链式执行
    //默认写出到配置的本地路径下
    //可配置为写出到oss云端
    public void generate() {
        for (PdfConfigurer configurer : configurers) {
            configurer.executeAndClose ();
            if (configurer.isLastNode ()) {
                toFile ();
            }
        }
    }

    public PdfReader getPdfReader() {
        try {
            reader = new PdfReader (getInputStream (), ownerPassword);
        } catch (IOException e) {
            throw new RuntimeException ("初始化PDF阅读器异常", e);
        }
        return reader;
    }

    public PdfStamper getStamper() {
        try {
            this.getPdfReader ();
            stamper = new PdfStamper (reader, bos);
        } catch (Exception e) {
            throw new RuntimeException ("初始化Stamper异常", e);
        }
        return stamper;
    }

    public void close() {
        pdfClose ();
        count ();
    }

    public void toFile() {
        ByteArrayInputStream bais = new ByteArrayInputStream (bos.toByteArray ());
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream (outputPdfPath);
            IOUtils.copy (bais, fos);
        } catch (IOException e) {
            throw new RuntimeException ("写入文件失败: ", e);
        } finally {
            IOUtils.closeQuietly (bais);
            IOUtils.closeQuietly (fos);
            bos.reset ();
        }
    }

    public List<PdfConfigurer> getConfigurers() {
        return configurers;
    }


    public ByteArrayOutputStream getBos() {
        return bos;
    }

    public void setOutputPdfPath(String outputPdfPath) {
        this.outputPdfPath = outputPdfPath;
    }

    public void setOwnerPassword(String ownerPassword) {
        this.ownerPassword = ownerPassword.getBytes (StandardCharsets.UTF_8);
    }

    public String getTemplatePath() {
        return templatePath;
    }


    private void count() {
        count++;
    }

    /**
     * 如果pdf只操作一次 那么获取pdf对象就是模板对象
     * 如果超过一次 那么获取pdf对象应该是第一次操作的输出位置
     */
    private InputStream getInputStream() throws IOException {
        InputStream inputStream = count > 0 ? new ByteArrayInputStream (bos.toByteArray ()) : RelateUtil.getInputStream (templatePath);
        bos.reset ();
        return inputStream;

    }


    protected void pdfClose() {
        try {
            if (stamper != null) {
                stamper.close ();
            }

            if (reader != null) {
                reader.close ();
            }
        } catch (Exception e) {
            log.error ("关闭pdf流异常: ", e);
        }
    }


    public static PdfProcessor builder() {
        return new PdfProcessor ();
    }

    public static PdfProcessor builder(Class<? extends PdfProcessor> clazz) {
        try {
            return clazz.newInstance ();
        } catch (InstantiationException | IllegalAccessException e) {
            log.error ("初始化异常", e);
            throw new PdfException ("初始化异常");
        }
    }


    public PdfProcessor template(String templatePdfPath) {
        this.templatePath = templatePdfPath;
        return this;
    }

    public PdfProcessor output(String outputPdfPath) {
        this.outputPdfPath = outputPdfPath;
        return this;
    }

    public PdfProcessor ownerPassword(String password) {
        this.ownerPassword = password.getBytes (StandardCharsets.UTF_8);
        return this;
    }


    /**
     * 默认注入PDF模板
     */
    public FillPdfTemplateConfigurer fillTemplate() {
        return getOrApply (new FillPdfTemplateConfigurer (this));
    }
    public AntiFakeConfigurer antiFake() {
        return getOrApply (new AntiFakeConfigurer (this));
    }
    public WaterMarkConfigurer waterMark() {
        return getOrApply (new WaterMarkConfigurer (this));
    }
    public SignatureConfigurer signature() {
        return getOrApply (new SignatureConfigurer (this));
    }
    public EncryptConfigurer encrypt() {
        return getOrApply (new EncryptConfigurer (this));
    }
    /**
     * 可以注入PDF模板以及PDF的子类
     */
    public <T extends FillPdfTemplateConfigurer> T fillTemplate(T configurer) {
        configurer.setProcessor (this);
        return getOrApply (configurer);
    }
    public <T extends AntiFakeConfigurer> T antiFake(T configurer) {
        configurer.setProcessor (this);
        return getOrApply (configurer);
    }
    public <T extends WaterMarkConfigurer> T waterMark(T configurer) {
        configurer.setProcessor (this);
        return getOrApply (configurer);
    }
    public <T extends SignatureConfigurer> T signature(T configurer) {
        configurer.setProcessor (this);
        return getOrApply (configurer);
    }
    public <T extends EncryptConfigurer> T encrypt(T configurer) {
        configurer.setProcessor (this);
        return getOrApply (configurer);
    }


    private <T extends PdfConfigurer> T getOrApply(T configurer) {
        boolean contains =configurers.contains (configurer);
        return contains ? configurer : add (configurer);
    }

    private <T extends PdfConfigurer> T add(T configurer) {
        configurers.add (configurer);
        return configurer;
    }


    public PdfProcessor build() {
        Assert.notNull (templatePath, "templatePath cannot be null");
        sort ();
        List<PdfConfigurer> configurers = getConfigurers ();
        PdfConfigurer pdfConfigurer = configurers.get (configurers.size () - 1);
        pdfConfigurer.setLastNode ();
        return this;
    }


    public String getOutputPdfPath() {
        return outputPdfPath;
    }

}
